<?php
// ------------------------------------------------------------------------
// |@Author       : Jarmin <edshop@qq.com>
// |@----------------------------------------------------------------------
// |@Date         : 2023-05-08 15:10:53
// |@----------------------------------------------------------------------
// |@LastEditTime : 2023-06-03 22:43:16
// |@----------------------------------------------------------------------
// |@LastEditors  : Jarmin <jarmin@ladmin.cn>
// |@----------------------------------------------------------------------
// |@Description  : 
// |@----------------------------------------------------------------------
// |@FilePath     : User.php
// |@----------------------------------------------------------------------
// |@Copyright (c) 2023 http://www.ladmin.cn   All rights reserved. 
// ------------------------------------------------------------------------
namespace app\api\model;

use think\admin\Model;

class User extends Model
{
    // 设置隐藏字段
    protected $hidden = ['password'];

    /**
     * 定义关联模型，角色和用户是一对多关联
     * @return \think\model\relation\hasMany
     */
    public function roles()
    {
        return $this->hasMany(Role::class, 'id', 'role_list');
    }

    public function auths()
    {
        return $this->hasMany(Auth::class, 'role_id', 'role_list');
    }
    
    /**
     * 用户登录
     * @param array $data 登录数据
     * @return array 登录结果，包含 code 状态码、message 提示信息和 data 数据
    */
    public static function tokenUser(array $data): array
    {
        // 根据用户名、邮箱或电话号码查询用户信息
        $user = static::mk()->where('username|email|phone', $data['username'])->find();
        // 判断用户信息是否存在，不存在则返回提示信息
        if (!$user) {
            return ['code' => 100, 'message' => lang('user.user_not_found')];
        }
        // 判断用户是否被禁用，若被禁用则返回提示信息
        if ($user->status === 0) {
            return ['code' => 102, 'message' => lang('user.user_banned')];
        }
        // 验证用户名与密码的正确性，若密码不正确则返回错误提示
        if (static::encryptPassword($data['password'], $user->salt) !== $user->password) {
            return ['code' => 101,'message' => lang('user.user_password_fail')];
        }
        // 防止密码盐暴露，对密码盐进行MD5加密
        $user->salt = md5($user->salt . md5($user->salt));
        // 生成唯一的 Token
        $token = sha1(md5(uniqid(md5(microtime(true)), true)));
        // 判断用户是否已经登录，若已登录则将之前的 Token 终止
        $is_login = cache('user_info:' . $user->id);
        if ($is_login) self::logoutUser($is_login);
        // 判断是不是超级管理员，如果是超级管理员则获取全部角色ID
        if ($user->id == 1) {
            $user->role_list = json_encode(Role::column('id'));
        }
        // 将缓存时间也写入到缓存中
        $user->token_time = $data['expires'];
        // 将 Token 存储到 Redis 缓存中
        cache('user_info:' . $user->id, $token, $data['expires']);
        cache('user_info:' . $token, $user->toArray(), $data['expires']);
        // 更新数据库中 login_token 字段的值
        try {
            static::mk()->where('id', $user->id)->update(['login_token'=>$token]);
            return ['code' => 200,'message' => lang('user.user_login_success'),'data' => ['token'=>$token, 'user_info'=>$user->toArray()]];
        } catch (\Exception $e) {
            return ['code' => $e->getCode(),'message' => $e->getMessage()];
        }
    }

    /**
     * 用户退出登录
     * @param string $token 用户 Token
     * @return array 退出结果，包含 code 状态码、message 提示信息和 data 数据
     */
    public static function logoutUser(string $token): array
    {
        // 从 Redis 缓存中获取用户信息
        $user = cache('user_info:' . $token);
        if (!$user) return ['code' => 103,'message' => lang('user.user_not_login')];
        // 清除绑定用户 ID 的缓存与 Token 对应的缓存信息
        cache('user_info:' . $user['id'], NULL);
        cache('user_info:' . $token,      NULL);
        return $user;
    }

    /**
     * 获取用户菜单和所属角色权限信息
     * @param string $token 用户 token
     * @return array 获取到的菜单和角色信息
     */
    public function getUserMenu(string $token): array
    {
        // 从缓存中获取用户信息
        $user = cache('user_info:' . $token);
        if (!$user) return [];
        // 根据用户信息获取菜单和所属角色权限信息
        $menus = Menu::getUserMenu($user['id'], json_decode($user['role_list']));
        $menus['dashboardGrid'] = Role::getUserRole($user['id'], json_decode($user['role_list']));
        // 将api_auth 写入redis缓存
        $user['api_auth'] = $menus['api_auth'];
        cache('user_info:' . $token, $user, $user['token_time']);
        unset($menus['api_auth']);
        // 返回获取到的数据
        return $menus;
    }
    
    /**
     * 获取用户列表信息，包含所属角色信息
     * @param array $data 请求参数，包含用户名称、页码和每页数据条数
     * @return array 返回用户列表数据
     */
    public static function listUser(array $data): array
    {
        // 构建查询条件
        $where = [];
        if (!empty($data['name'])) {
            $where = [["name|username", "like", "%" . $data['name'] . "%"]];
        }
        if (!empty($data['dept_id'])) {
            $where = [["dept_id", "=", $data['dept_id']]];
        }
        if (empty($where)) {
            $where = [["id", ">", 0]];
        }
        // 构建分页参数
        $limit = ['list_rows'=>!empty($data['pageSize']) ? $data['pageSize'] : 20, 'page'=>!empty($data['page']) ? $data['page'] : 1];
        // 使用关联模型一次性获取所有用户及其所属角色信息
        $users = static::mk()->with('roles')->where($where)->order('id', 'desc')->paginate($limit)->toArray();
        // 遍历查询结果，将关联查询出来的多个角色名拼接成一个字符串，并存放在一个新的role_name字段中
        foreach ($users['data'] as &$user)
        {
            // role_list 转为数组格式
            $user['role_list'] = json_decode($user['role_list']) ?? [];
            $user['role_names'] = self::getRolesName($user['role_list']);
        }
        // 返回查询结果
        return $users;
    }
    
    /**
     * 新增或更新用户信息
     * @param  array  $data  请求参数，包含用户信息
     * @return array         返回新增或更新的用户信息
     */
    public static function saveUser(array $data): array
    {
        $fn = function () use ($data) {
            if (empty($data['id'])) {
                // 如果没有id值，则插入数据并获取新增的id
                $data['salt'] = uuid();
                $data['password'] = md5(md5($data['password']) . $data['salt']);
                $data['create_at'] = date('Y-m-d H:i:s', time());
            }

            $data['role_list'] = json_encode($data['role_list'] ?? []);

            if (empty($data['id'])) {
                $data['id'] = static::mk()->strict(false)->insertGetId($data);
                unset($data['salt']);
                unset($data['password']);
            } else {
                static::mk()->strict(false)->where('id', $data['id'])->update($data);
            }

            $result = $data;
            $result['role_list'] = json_decode($data['role_list'], true);
            $result['role_names'] = self::getRolesName(json_decode($data['role_list'], true));

            return $result;
        };

        return static::mk()->runTransaction($fn);
    }
    
    /**
     * 根据角色ID列表获取角色名称
     * @param array $role_list 角色ID列表
     * @return string 角色名称，使用逗号分隔
     */
    private static function getRolesName(array $role_list): string
    {
        // 通过 Role 模型查询角色列表
        $roles = Role::whereIn('id', $role_list)->select();
        return $roles->isEmpty() ? '' : implode(',', $roles->column('name'));
    }

    /**
     * 更新用户状态
     * @param  array $data 角色状态数据，包含 id 和 status 等字段
     * @return int         返回更新的状态数
     */
    public static function stateUser(array $data): int
    {
        $fn = function () use ($data) {
            // 根据 $data 中的 id 值，更新角色状态为 $data 中的 status 值。
            $status = static::mk()->where('id', $data['id'])->update(['status' => $data['status']]);
            return $status;
        };

        return static::mk()->runTransaction($fn);
    }

    /**
     * 批量重置用户密码
     * @param  array  $data 待重置用户列表
     * @return array        重置操作结果，成功为批量写入操作结果数组，失败为空数组
     */
    public static function resetUser(array $data): array
    {
        // 过滤掉传入的空数据
        $data = array_filter($data, function($v) {
            return !empty($v) || $v === 0 || $v === '0';
        });

        $status = [
            'code' => 200,
            'message' => lang('user.reset_success'),
            'data' => [],
        ];

        $fn = function () use ($data, &$status) {
            // 获取选中的用户信息
            $info = static::mk()->whereIn('id', $data['ids'])->column('id, password, salt');
            // 检查原密码是否正确，如果错误，则返回错误信息
            $validatePassword = !empty($data['password']); // 是否需要验证密码
            foreach ($info as $id => $user) {
                if ($validatePassword && static::encryptPassword($data['password'], $user['salt']) !== $user['password']) {
                    $status = [
                        'code' => 422,
                        'message' => lang('user.old_password_error'),
                        'data' => [],
                    ];
                    return;
                }
                $info[$id]['password'] = static::encryptPassword($data['newpassword'], $user['salt']);
            }
            // 批量保存数据
            try {
                $model = new self;
                $model->saveAll($info);
            } catch (\Exception $e) {
                $status = [
                    'code' => 500,
                    'message' => lang('user.reset_fail'),
                    'data' => ['error' => $e->getMessage()],
                ];
                return;
            }
        };

        // 事务处理
        static::mk()->runTransaction($fn);

        return $status;
    }

    /**
     * 批量删除用户
     * @param  array  $data 待删除用户列表
     * @return bool         删除操作结果，成功为 true，失败为 false
     */
    public static function removeUser(array $data): bool
    {
        $fn = function () use ($data) {
            static::mk()->whereIn('id', $data['ids'])->delete();
            return true;
        };
        
        return static::mk()->runTransaction($fn);
    }
}